/*
 * eeprom_i2c.c
 *
 * Copyright 2022 dh33ex <dh33ex@riseup.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA or see <http://www.gnu.org/licenses/>.
 *
 *
 */

#ifndef __msp430_h_
    #include <msp430.h>
#endif
#include "eeprom_i2c.h"


int ptr_buf;
char i2c_buffer[68];


void EEPROM_init(unsigned char i2c_address) {
    /* setup B0 for I2C */
    UCB0CTL1 |= UCSWRST;                /* put B0 in SW RST */

    UCB0CTL1 |= UCSSEL_2;               /* choose SMCLK */
    UCB0BR0 = 10;                       /* set presalar to 10 */
    UCB0BR1 = 0;

    UCB0CTL0 |= UCMODE_3;               /* put into I2C mode */
    UCB0CTL0 |= UCMST;                  /* set as master */
    UCB0I2CSA = i2c_address;            /* set slave address */

    /* setup ports */
    P1SEL |= BIT7;                      /* P1.7 = SDA */
    P1SEL2 |= BIT7;
    P1SEL |= BIT6;                      /* P1.6 = SCL */
    P1SEL2 |= BIT6;

    UCB0CTL1 &= ~UCSWRST;               /* take B0 out of SW RST */
}


void EEPROM_write_setup(void) {
    UCB0CTL1 |= UCTR;                   /* set transmit mode */
    IFG2 &= ~UCB0TXIFG;                 /* clear transmit interrupt flag */
    IE2 &= ~UCB0RXIE;                   /* disable recive interrupt */
    IE2 |= UCB0TXIE;                    /* enable transmit interrupt */
}


void EEPROM_read_setup(void) {
    UCB0CTL1 &= ~UCTR;                  /* set recive mode */
    IFG2 &= ~UCB0RXIFG;                 /* clear recive interrupt flag */
    IE2 &= ~UCB0TXIE;                   /* disable transmit interrupt */
    IE2 |= UCB0RXIE;                    /* enable receive interrupt */
}


void EEPROM_ACK_polling(void) {
    while (UCB0STAT & UCBUSY);          /* wait until I2C is free */

    UCB0CTL1 |= UCTR;                   /* set transmit mode */

    do {
        UCB0STAT &= ~UCNACKIFG;         /* clear NACK flag */
        UCB0CTL1 |= UCTXSTT;            /* send start message manually */
        while (UCB0CTL1 & UCTXSTT) {    /* wait until start message sent */
            if (!(UCB0STAT & UCNACKIFG)) {      /* break if ACK recived */
                break;
            }
        }
        UCB0CTL1 |= UCTXSTP;            /* send stop message manually */
        while (UCB0CTL1 & UCTXSTP);     /* wait until stop message sent */
    } while (UCB0STAT & UCNACKIFG);     /* wait until ACK recived */
}


void EEPROM_sequential_read(unsigned char size, unsigned short addr, unsigned char *buffer) {
    while (UCB0STAT & UCBUSY);          /* wait until I2C is free */

    i2c_buffer[1] = addr >> 8;          /* save high byte */
    i2c_buffer[0] = addr & 0xFF;        /* save low byte */
    ptr_buf = 1;                        /* set pointer */

    EEPROM_write_setup();               /* setup to write operation */
    UCB0CTL1 |= UCTXSTT;                /* send start message manually */
    __bis_SR_register(LPM0_bits + GIE); /* enter interrupt w/ LP0 */

    EEPROM_read_setup();                /* setup to read operation */
    UCB0CTL1 |= UCTXSTT;                /* send start message manually */
    while (UCB0CTL1 & UCTXSTT);         /* wait until start message sent */

    /* read bytes excluding the last one */
    unsigned short cnt_size;
    for (cnt_size = 0 ; cnt_size < size - 1; cnt_size++) {
        __bis_SR_register(LPM0_bits + GIE); /* enter interrupt w/ LP0 */
        buffer[cnt_size] = i2c_buffer[0];   /* read byte */
    }

    /* send stop message and read last byte */
    UCB0CTL1 |= UCTXSTP;                /* send stop message manually */
    __bis_SR_register(LPM0_bits + GIE); /* enter interrupt w/ LP0  */
    while (UCB0CTL1 & UCTXSTP);         /* wait until stop message sent */
    buffer[cnt_size] = i2c_buffer[0];   /* read byte */
}


void EEPROM_page_write(unsigned char size, unsigned short addr, unsigned char *buffer, unsigned char ack) {
    while (UCB0STAT & UCBUSY);          /* wait until I2C is free */

    i2c_buffer[size + 1] = addr >> 8;   /* save high byte */
    i2c_buffer[size] = addr & 0xFF;     /* save low byte */
    ptr_buf = size + 1;                 /* set pointer */

    unsigned short cnt_size;
    for (cnt_size = 0; cnt_size < size; cnt_size++) {
        i2c_buffer[size-cnt_size - 1] = buffer[cnt_size];
    }

    EEPROM_write_setup();               /* setup to write operation */
    UCB0CTL1 |= UCTXSTT;                /* send start message manually */
    __bis_SR_register(LPM0_bits + GIE); /* enter interrupt w/ LP0 */
    UCB0CTL1 |= UCTXSTP;                /* send stop message manually */
    while (UCB0CTL1 & UCTXSTP);         /* wait until stop message sent */
    if (ack) {
        EEPROM_ACK_polling();           /* determine end data write cycle using ACK polling */
    } else {
        __delay_cycles(5000);           /* wait 5ms (1 Mhz) */
    }
}


/*---- ISR ----*/
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = USCIAB0TX_VECTOR
__interrupt void USCIAB0TX_I2C_ISR(void) {
#elif defined(__GNUC__)
void __attribute__ ((interrupt(USCIAB0TX_VECTOR))) USCIAB0TX_I2C_ISR (void) {
#else
#error Compiler not supported!
#endif
    if (UCB0TXIFG & IFG2) {
        UCB0TXBUF = i2c_buffer[ptr_buf];            /* Load TX buffer */
        ptr_buf--;                                  /* Decrement TX byte counter */
        if (ptr_buf < 0) {
            while (!(IFG2 & UCB0TXIFG));
            IE2 &= ~UCB0TXIE;                       /* disable interrupts */
            IFG2 &= ~UCB0TXIFG;                     /* Clear USCI_B0 TX int flag */
            __bic_SR_register_on_exit(LPM0_bits);   /* Exit LPM0 */
        }
    } else if (UCB0RXIFG & IFG2) {
        i2c_buffer[0] = UCB0RXBUF;                  /* store received data in buffer */
        __bic_SR_register_on_exit(LPM0_bits);       /* Exit LPM0 */
    }
}
